package com.roamer.audition.server.query.base;

import com.roamer.audition.server.entity.base.BaseEntity;
import com.roamer.util.BeanUtils;
import com.roamer.util.StringUtils;
import lombok.NoArgsConstructor;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.predicate.CompoundPredicate;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import static javax.persistence.criteria.Predicate.BooleanOperator;

/**
 * 基础查询
 *
 * @author roamer
 * @version V1.0
 * @date 2019/8/25 11:14 下午
 */
@NoArgsConstructor
public abstract class BaseQuery<T extends BaseEntity> implements Serializable {
    private static final long serialVersionUID = 3300840436994584959L;

    /**
     * 将查询转换成Specification
     *
     * @return {@code Specification<T>}
     */
    public abstract Specification<T> toSpec();

    /**
     * 动态查询and连接
     *
     * @return {@code Specification<T>}
     */
    protected Specification<T> toSpecWithAnd() {
        return this.toSpecWithOperator(BooleanOperator.AND);
    }

    /**
     * 动态查询or连接
     *
     * @return {@code Specification<T>}
     */
    protected Specification<T> toSpecWithOr() {
        return this.toSpecWithOperator(BooleanOperator.OR);
    }

    /**
     * logicType or/and
     *
     * @param operator 操作类型
     *
     * @return {@code Specification<T>}
     */
    private Specification<T> toSpecWithOperator(BooleanOperator operator) {
        BaseQuery outerThis = this;
        return (root, query, builder) -> {
            Class clazz = outerThis.getClass();
            //获取查询类Query的所有字段,包括父类字段
            List<Field> fields = BeanUtils.getAllFieldsWithRoot(clazz);
            List<Predicate> predicates = new ArrayList<>(fields.size());
            try {
                for (Field field : fields) {
                    if (!supports(field)) {
                        continue;
                    }
                    //获取字段上的@QueryCondition注解
                    QueryCondition qw = field.getAnnotation(QueryCondition.class);
                    if (qw == null) {
                        continue;
                    }

                    // 获取字段名
                    String column = qw.column();
                    //如果主注解上column为默认值"",则以field为准
                    if ("".equals(column)) {
                        column = field.getName();
                    }
                    field.setAccessible(true);

                    // nullable
                    Object value = field.get(outerThis);
                    //如果值为null,注解未标注nullable,跳过
                    if (value == null && !qw.nullable()) {
                        continue;
                    }
                    // can be empty
                    if (value != null) {
                        Class<?> valueClass = value.getClass();
                        if (String.class.isAssignableFrom(valueClass)) {
                            //如果值为"" && notEmpty == true 跳过
                            if (StringUtils.isEmpty((String) value) && qw.notEmpty()) {
                                continue;
                            }
                        } else if (Collection.class.isAssignableFrom(valueClass)) {
                            if (((Collection) value).isEmpty() && qw.notEmpty()) {
                                continue;
                            }
                            if (qw.queryEnums().length > 0) {
                                value = getCollectionValue((Collection) value, qw.queryEnums()[0]);
                            }
                        }
                    }

                    //通过注解上func属性,构建路径表达式
                    Path path = root.get(column);
                    getPredicate(builder, qw, path, value).ifPresent(predicates::add);

                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return new CompoundPredicate((CriteriaBuilderImpl) builder, operator, predicates.toArray(new Predicate[0]));
        };
    }

    private Optional<Predicate> getPredicate(CriteriaBuilder builder, QueryCondition qw, Path path, Object value) {
        Predicate predicate = null;
        switch (qw.func()) {
            case eq:
                predicate = builder.equal(path, value);
                break;
            case notEqual:
                predicate = builder.notEqual(path, value);
                break;
            case in:
                predicate = path.in(value);
                break;
            case notIn:
                predicate = builder.not(path.in(value));
                break;
            case like:
                predicate = builder.like(path, "%" + value + "%");
                break;
            case startsWith:
                predicate = builder.like(path, value + "%");
                break;
            case endsWith:
                predicate = builder.like(path, "%" + value);
                break;
            case notLike:
                predicate = builder.notLike(path, "%" + value + "%");
                break;
            case gt:
                predicate = builder.gt(path, (Number) value);
                break;
            case lt:
                predicate = builder.lt(path, (Number) value);
                break;
            case ge:
                predicate = builder.ge(path, (Number) value);
                break;
            case le:
                predicate = builder.le(path, (Number) value);
                break;
            case greaterThan:
                predicate = builder.greaterThan(path, (Comparable) value);
                break;
            case greaterThanOrEqualTo:
                predicate = builder.greaterThanOrEqualTo(path, (Comparable) value);
                break;
            case lessThan:
                predicate = builder.lessThan(path, (Comparable) value);
                break;
            case lessThanOrEqualTo:
                predicate = builder.lessThanOrEqualTo(path, (Comparable) value);
                break;
            default:
                break;
        }
        return Optional.ofNullable(predicate);
    }

    /**
     * 是否支持
     *
     * @param field 属性
     *
     * @return {@code List<?>} 转换后的枚举
     */
    protected boolean supports(Field field) {
        return true;
    }

    private List<?> getCollectionValue(Collection value, QueryCondition.QueryEnum queryEnum) {
        List<Object> enumList = new ArrayList<>(value.size());
        Class<?> valueClass = value.iterator().next().getClass();
        Class<? extends Enum<?>> enumClass = queryEnum.enumClass();
        String enumMethod = queryEnum.enumMethod();
        Method method = null;
        try {
            // 转换
            enumClass = queryEnum.enumClass();
            enumMethod = queryEnum.enumMethod();
            method = enumClass.getMethod(enumMethod, valueClass);
            if (!enumClass.equals(method.getReturnType())) {
                throw new RuntimeException(
                        StringUtils.formatIfArgs("%s method return is not boolean type in the %s class", enumMethod,
                                                 enumClass));
            }
            // 用于验证的函数是否是static修饰的
            if (!Modifier.isStatic(method.getModifiers())) {
                throw new RuntimeException(
                        StringUtils.formatIfArgs("%s method is not static method in the %s class", enumMethod,
                                                 enumClass));
            }
            // 执行
            Iterator iterator = value.iterator();
            for (Object o; iterator.hasNext() && (o = iterator.next()) != null; ) {
                enumList.add(method.invoke(null, o));
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(
                    StringUtils.formatIfArgs("This %s(%s) method does not exist in the %s", enumMethod, valueClass,
                                             enumClass), e);
        }
        return enumList;
    }

}
